package cn.parker.excel.advice;

import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.util.IoUtils;
import cn.idev.excel.write.builder.ExcelWriterBuilder;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.handler.WriteHandler;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import cn.parker.excel.annotation.ExportExcel;
import cn.parker.excel.convert.ExcelBigNumberConvert;
import cn.parker.excel.core.CellMergeStrategy;
import cn.parker.excel.core.ExcelDownHandler;
import cn.parker.excel.handler.DataWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <pre>
 * 自动将返回list数据导出为excel
 * </pre>
 *
 * @author Parker
 * @version 1.0.0
 * @since 2025-07-30 1:43
 */
@Slf4j
@RestControllerAdvice
public class ExportExcelResponseAdvice implements ResponseBodyAdvice<Object> {
    private final Pattern pattern = Pattern.compile("^#\\{(.*?)}$");

    private final ExpressionParser parser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private String fileName;
    private String sheetName;
    private Class<?> modelClass;
    private String template;
    private boolean cellMerge;
    private boolean compress;
    private int sheetDataSize;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        boolean hasMethodAnnotation = returnType.hasMethodAnnotation(ExportExcel.class);
        if (hasMethodAnnotation) {
            ExportExcel exportExcel = returnType.getMethodAnnotation(ExportExcel.class);
            fileName = exportExcel.fileName();
            sheetName = exportExcel.sheetName();
            modelClass = exportExcel.modelClass();
            template = exportExcel.template();
            cellMerge = exportExcel.cellMerge();
            compress = exportExcel.compress();
            sheetDataSize = exportExcel.sheetDataSize();
        }
        return hasMethodAnnotation;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        if (!(body instanceof List)) {
            log.warn("返回值不是 List 类型，无法导出为 Excel");
            return body;
        }

        List<?> dataList = (List<?>) body;
        try {
            HttpServletRequest rawRequest = null;
            HttpServletResponse rawResponse = null;

            if (request instanceof ServletServerHttpRequest) {
                rawRequest = ((ServletServerHttpRequest) request).getServletRequest();
            }

            if (response instanceof ServletServerHttpResponse) {
                rawResponse = ((ServletServerHttpResponse) response).getServletResponse();
            }

            if (rawRequest == null || rawResponse == null) {
                log.error("获取原生请求或响应对象失败");
                return body;
            }

            rawResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
            fileName = evaluateExpression(returnType, fileName, rawRequest);
            sheetName = evaluateExpression(returnType, sheetName, rawRequest);

            // 构建Excel文件名
            String excelFileName = fileName + ".xlsx";
            // 创建内存流用于存储Excel数据
            ByteArrayOutputStream excelOutputStream = new ByteArrayOutputStream();

            ClassPathResource resource = null;
            if (StringUtils.hasLength(template)) {
                template = evaluateExpression(returnType, template, rawRequest);
                resource = new ClassPathResource(template);
            }

            ExcelWriterBuilder builder = FastExcel.write(excelOutputStream, modelClass);
            if (resource != null && resource.exists()) {
                byte[] templateBytes = IoUtils.toByteArray(resource.getInputStream());
                InputStream templateStream = new ByteArrayInputStream(templateBytes);
                builder.withTemplate(templateStream);
                builder.autoCloseStream(true).sheet().doWrite(dataList);
            } else {
                builder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                        .registerConverter(new ExcelBigNumberConvert())
                        .registerWriteHandler(new DataWriteHandler(modelClass));

                if (sheetDataSize <= 0) {
                    ExcelWriterSheetBuilder writerSheetBuilder = builder.autoCloseStream(true).sheet(sheetName);
                    if (cellMerge) {
                        writerSheetBuilder.registerWriteHandler(new CellMergeStrategy(dataList, true, sheetName, 0));
                    }
                    writerSheetBuilder.registerWriteHandler(new ExcelDownHandler(null));
                    writerSheetBuilder.doWrite(dataList);
                } else {
                    ExcelWriter excelWriter = builder.autoCloseStream(true).build();
                    int sheetNum = dataList.size() / sheetDataSize;
                    sheetNum = dataList.size() % sheetDataSize == 0 ? sheetNum : sheetNum + 1;
                    for (int i = 0; i < sheetNum; i++) {
                        List<?> sheetDataList = dataList.subList(i * sheetDataSize, Math.min((i * sheetDataSize + sheetDataSize), dataList.size()));
                        WriteSheet writeSheet = FastExcel.writerSheet(i, sheetName + "_" + i).build();
                        List<WriteHandler> customWriteHandlerList = writeSheet.getCustomWriteHandlerList();
                        if (cellMerge) {
                            customWriteHandlerList.add(new CellMergeStrategy(sheetDataList, true, sheetName + "_" + i, i));
                        }
                        customWriteHandlerList.add(new ExcelDownHandler(null));
                        writeSheet.setCustomWriteHandlerList(customWriteHandlerList);
                        excelWriter.write(sheetDataList, writeSheet);
                    }
                    excelWriter.finish();
                }

            }

            // 如果需要压缩
            if (compress) {
                // 创建ZIP文件名
                String zipFileName = fileName + ".zip";
                // 创建内存流用于存储ZIP数据
                ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
                try (ZipOutputStream zipOut = new ZipOutputStream(zipOutputStream)) {
                    ZipEntry zipEntry = new ZipEntry(excelFileName);
                    zipOut.putNextEntry(zipEntry);
                    zipOut.write(excelOutputStream.toByteArray());
                    zipOut.closeEntry();
                }

                // 设置响应头
                rawResponse.setContentType("application/zip");
                rawResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=" + URLEncoder.encode(zipFileName, StandardCharsets.UTF_8.name())
                                .replaceAll("\\+", "%20"));

                // 写入ZIP数据到响应
                rawResponse.getOutputStream().write(zipOutputStream.toByteArray());
            } else {
                // 不压缩时直接返回Excel文件
                rawResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                rawResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=" + URLEncoder.encode(excelFileName, StandardCharsets.UTF_8.name())
                                .replaceAll("\\+", "%20"));
                rawResponse.getOutputStream().write(excelOutputStream.toByteArray());
            }

        } catch (IOException e) {
            log.error("Excel 导出失败", e);
            throw new RuntimeException("Excel 导出失败", e);
        }

        return null;
    }


    /**
     * 解析SpEL表达式
     */
    private String evaluateExpression(MethodParameter parameter, String expression, HttpServletRequest request) {
        Matcher matcher = pattern.matcher(expression);
        if (!matcher.matches()) {
            return expression;
        }
        expression = matcher.group(1);
        StandardEvaluationContext context = new MethodBasedEvaluationContext(null,
                parameter.getMethod(), new Object[0], parameterNameDiscoverer);
        // 可以访问Request中的方法
        context.setVariable("request", request);
        // 解析表达式
        return parser.parseExpression(expression).getValue(context, String.class);
    }
}
